﻿using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using log4net;
using ScpControl.Driver;
using ScpControl.Usb;

namespace ScpControl
{
    /// <summary>
    ///     Low-level representation of an Scp-compatible Usb device.
    /// </summary>
    public partial class ScpDevice
    {
        protected static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        #region Ctors

        protected ScpDevice()
        {
        }

        protected ScpDevice(Guid Class)
        {
            this._class = Class;
        }

        #endregion

        protected bool IsActive { get; set; }

        public string Path { get; protected set; }

        public short VendorId { get; protected set; }

        public short ProductId { get; protected set; }

        protected void GetHardwareId(string devicePath)
        {
            short vid, pid;

            GetHardwareId(devicePath, out vid, out pid);

            // get values
            VendorId = vid;
            ProductId = pid;
        }

        public static void GetHardwareId(string devicePath, out short vendorId, out short productId)
        {
            // regex to extract vendor ID and product ID from hardware ID string
            var regex = new Regex("VID_([0-9A-Z]{4})&PID_([0-9A-Z]{4})", RegexOptions.IgnoreCase);
            // matched groups
            var matches = regex.Match(devicePath).Groups;

            // very basic check
            if (matches.Count < 3)
            {
                vendorId = productId = 0;
                return;
            }

            // get values
            vendorId = short.Parse(matches[1].Value, NumberStyles.HexNumber);
            productId = short.Parse(matches[2].Value, NumberStyles.HexNumber);
        }

        public virtual bool Open(int instance = 0)
        {
            var devicePath = string.Empty;

            if (FindDevice(_class, ref devicePath, instance))
            {
                Open(devicePath);
            }

            return IsActive;
        }

        public virtual bool Open(string devicePath)
        {
            GetHardwareId(devicePath);

            Path = devicePath.ToUpper();

            if (GetDeviceHandle(Path))
            {
                if (WinUsbWrapper.Initialize(FileHandle, ref _winUsbHandle))
                {
                    if (InitializeDevice())
                    {
                        IsActive = true;
                    }
                    else
                    {
                        WinUsbWrapper.Free(_winUsbHandle);
                        _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE;
                    }
                }
                else
                {
                    CloseHandle(FileHandle);
                }
            }

            return IsActive;
        }

        public virtual bool Start()
        {
            return IsActive;
        }

        public virtual bool Stop()
        {
            IsActive = false;

            if (!(_winUsbHandle == (IntPtr)INVALID_HANDLE_VALUE))
            {
                WinUsbWrapper.AbortPipe(_winUsbHandle, IntIn);
                WinUsbWrapper.AbortPipe(_winUsbHandle, BulkIn);
                WinUsbWrapper.AbortPipe(_winUsbHandle, BulkOut);

                WinUsbWrapper.Free(_winUsbHandle);
                _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE;
            }

            if (FileHandle != IntPtr.Zero)
            {
                CloseHandle(FileHandle);

                FileHandle = IntPtr.Zero;
            }

            return true;
        }

        public virtual bool Close()
        {
            return Stop();
        }

        protected static ushort ToValue(UsbHidClassDescriptorType type, byte index = 0x00)
        {
            return BitConverter.ToUInt16(new[] { (byte)index, (byte)type }, 0);
        }

        protected static ushort ToValue(UsbHidReportRequestType type)
        {
            return (ushort) ((byte) type << 8 | (byte) 0x00);
        }

        protected static ushort ToValue(UsbHidReportRequestType type, UsbHidReportRequestId id)
        {
            return BitConverter.ToUInt16(new[] { (byte)id, (byte)type }, 0);
        }

        protected bool IsBitSet(byte value, int offset)
        {
            return ((value >> offset) & 1) == 0x01;
        }

        #region WinUSB wrapper methods

        protected bool ReadIntPipe(byte[] buffer, int length, ref int transfered)
        {
            return IsActive && WinUsbWrapper.ReadPipe(_winUsbHandle, IntIn, buffer, length, ref transfered, IntPtr.Zero);
        }

        protected bool ReadBulkPipe(byte[] buffer, int length, ref int transfered)
        {
            return IsActive && WinUsbWrapper.ReadPipe(_winUsbHandle, BulkIn, buffer, length, ref transfered, IntPtr.Zero);
        }

        protected bool WriteIntPipe(byte[] buffer, int length, ref int transfered)
        {
            return IsActive && WinUsbWrapper.WritePipe(_winUsbHandle, IntOut, buffer, length, ref transfered, IntPtr.Zero);
        }

        protected bool WriteBulkPipe(byte[] buffer, int length, ref int transfered)
        {
            return IsActive && WinUsbWrapper.WritePipe(_winUsbHandle, BulkOut, buffer, length, ref transfered, IntPtr.Zero);
        }

        protected bool SendTransfer(UsbHidRequestType requestType, UsbHidRequest request, ushort value, byte[] buffer,
            ref int transfered)
        {
            return SendTransfer((byte)requestType, (byte)request, value, buffer, ref transfered);
        }

        protected bool SendTransfer(byte requestType, byte request, ushort value, byte[] buffer, ref int transfered)
        {
            if (!IsActive) return false;

            var setup = new WINUSB_SETUP_PACKET
            {
                RequestType = requestType,
                Request = request,
                Value = value,
                Index = 0,
                Length = (ushort)buffer.Length
            };

            return WinUsbWrapper.ControlTransfer(_winUsbHandle, setup, buffer, buffer.Length, ref transfered, IntPtr.Zero);
        }

        #endregion

        #region Constant and Structure Definitions

        public const int SERVICE_CONTROL_STOP = 0x00000001;
        public const int SERVICE_CONTROL_SHUTDOWN = 0x00000005;
        public const int SERVICE_CONTROL_DEVICEEVENT = 0x0000000B;
        public const int SERVICE_CONTROL_POWEREVENT = 0x0000000D;

        public const int DBT_DEVICEARRIVAL = 0x8000;
        public const int DBT_DEVICEQUERYREMOVE = 0x8001;
        public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
        public const int DBT_DEVTYP_DEVICEINTERFACE = 0x0005;
        public const int DBT_DEVTYP_HANDLE = 0x0006;

        public const int PBT_APMRESUMEAUTOMATIC = 0x0012;
        public const int PBT_APMSUSPEND = 0x0004;

        public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x0000;
        public const int DEVICE_NOTIFY_SERVICE_HANDLE = 0x0001;
        public const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x0004;

        public const int WM_DEVICECHANGE = 0x0219;

        public const int DIGCF_PRESENT = 0x0002;
        public const int DIGCF_DEVICEINTERFACE = 0x0010;

        public delegate int ServiceControlHandlerEx(int Control, int Type, IntPtr Data, IntPtr Context);

        [StructLayout(LayoutKind.Sequential)]
        public class DEV_BROADCAST_DEVICEINTERFACE
        {
            internal int dbcc_size;
            internal int dbcc_devicetype;
            internal int dbcc_reserved;
            internal Guid dbcc_classguid;
            internal short dbcc_name;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class DEV_BROADCAST_DEVICEINTERFACE_M
        {
            public int dbcc_size;
            public int dbcc_devicetype;
            public int dbcc_reserved;

            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
            public byte[]
                dbcc_classguid;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
            public char[] dbcc_name;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class DEV_BROADCAST_HDR
        {
            public int dbch_size;
            public int dbch_devicetype;
            public int dbch_reserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        protected struct SP_DEVICE_INTERFACE_DATA
        {
            internal int cbSize;
            internal Guid InterfaceClassGuid;
            internal int Flags;
            internal IntPtr Reserved;
        }

        private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
        private const uint FILE_FLAG_OVERLAPPED = 0x40000000;
        private const uint FILE_SHARE_READ = 1;
        private const uint FILE_SHARE_WRITE = 2;
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const int INVALID_HANDLE_VALUE = -1;
        private const uint OPEN_EXISTING = 3;
        protected const uint DEVICE_SPEED = 1;
        protected const byte USB_ENDPOINT_DIRECTION_MASK = 0x80;

        protected enum POLICY_TYPE
        {
            SHORT_PACKET_TERMINATE = 1,
            AUTO_CLEAR_STALL = 2,
            PIPE_TRANSFER_TIMEOUT = 3,
            IGNORE_SHORT_PACKETS = 4,
            ALLOW_PARTIAL_READS = 5,
            AUTO_FLUSH = 6,
            RAW_IO = 7
        }



        protected enum USB_DEVICE_SPEED
        {
            UsbLowSpeed = 1,
            UsbFullSpeed = 2,
            UsbHighSpeed = 3
        }

        [StructLayout(LayoutKind.Sequential)]
        protected struct USB_CONFIGURATION_DESCRIPTOR
        {
            internal byte bLength;
            internal byte bDescriptorType;
            internal ushort wTotalLength;
            internal byte bNumInterfaces;
            internal byte bConfigurationValue;
            internal byte iConfiguration;
            internal byte bmAttributes;
            internal byte MaxPower;
        }



        protected const int DIF_PROPERTYCHANGE = 0x12;
        protected const int DICS_ENABLE = 1;
        protected const int DICS_DISABLE = 2;
        protected const int DICS_PROPCHANGE = 3;
        protected const int DICS_FLAG_GLOBAL = 1;

        [StructLayout(LayoutKind.Sequential)]
        protected struct SP_CLASSINSTALL_HEADER
        {
            internal int cbSize;
            internal int InstallFunction;
        }

        [StructLayout(LayoutKind.Sequential)]
        protected struct SP_PROPCHANGE_PARAMS
        {
            internal SP_CLASSINSTALL_HEADER ClassInstallHeader;
            internal int StateChange;
            internal int Scope;
            internal int HwProfile;
        }

        public enum WmDeviceChangeEvent : int
        {
            /// <summary>
            ///     A request to change the current configuration (dock or undock) has been canceled.
            /// </summary>
            DBT_CONFIGCHANGECANCELED = 0x0019,

            /// <summary>
            ///     The current configuration has changed, due to a dock or undock.
            /// </summary>
            DBT_CONFIGCHANGED = 0x0018,

            /// <summary>
            ///     A custom event has occurred.
            /// </summary>
            DBT_CUSTOMEVENT = 0x8006,

            /// <summary>
            ///     A device or piece of media has been inserted and is now available.
            /// </summary>
            DBT_DEVICEARRIVAL = 0x8000,

            /// <summary>
            ///     Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the
            ///     removal.
            /// </summary>
            DBT_DEVICEQUERYREMOVE = 0x8001,

            /// <summary>
            ///     A request to remove a device or piece of media has been canceled.
            /// </summary>
            DBT_DEVICEQUERYREMOVEFAILED = 0x8002,

            /// <summary>
            ///     A device or piece of media has been removed.
            /// </summary>
            DBT_DEVICEREMOVECOMPLETE = 0x8004,

            /// <summary>
            ///     A device or piece of media is about to be removed. Cannot be denied.
            /// </summary>
            DBT_DEVICEREMOVEPENDING = 0x8003,

            /// <summary>
            ///     A device-specific event has occurred.
            /// </summary>
            DBT_DEVICETYPESPECIFIC = 0x8005,

            /// <summary>
            ///     A device has been added to or removed from the system.
            /// </summary>
            DBT_DEVNODES_CHANGED = 0x0007,

            /// <summary>
            ///     Permission is requested to change the current configuration (dock or undock).
            /// </summary>
            DBT_QUERYCHANGECONFIG = 0x0017,

            /// <summary>
            ///     The meaning of this message is user-defined.
            /// </summary>
            DBT_USERDEFINED = 0xFFFF
        }

        #endregion

        #region Protected Data Members

        private Guid _class = Guid.Empty;

        protected IntPtr FileHandle = IntPtr.Zero;
        private IntPtr _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE;

        protected byte IntIn = 0xFF;
        protected byte IntOut = 0xFF;
        protected byte BulkIn = 0xFF;
        protected byte BulkOut = 0xFF;

        #endregion

        #region Static Helper Methods

        public enum Notified
        {
            Ignore = 0x0000,
            Arrival = 0x8000,
            QueryRemove = 0x8001,
            Removal = 0x8004
        };

        public static bool RegisterNotify(IntPtr form, Guid Class, ref IntPtr handle, bool window = true)
        {
            var devBroadcastDeviceInterfaceBuffer = IntPtr.Zero;

            try
            {
                var devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE();
                var size = Marshal.SizeOf(devBroadcastDeviceInterface);

                devBroadcastDeviceInterface.dbcc_size = size;
                devBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
                devBroadcastDeviceInterface.dbcc_reserved = 0;
                devBroadcastDeviceInterface.dbcc_classguid = Class;

                devBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(devBroadcastDeviceInterface, devBroadcastDeviceInterfaceBuffer, true);

                handle = RegisterDeviceNotification(form, devBroadcastDeviceInterfaceBuffer,
                    window ? DEVICE_NOTIFY_WINDOW_HANDLE : DEVICE_NOTIFY_SERVICE_HANDLE);

                Marshal.PtrToStructure(devBroadcastDeviceInterfaceBuffer, devBroadcastDeviceInterface);

                return handle != IntPtr.Zero;
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
            finally
            {
                if (devBroadcastDeviceInterfaceBuffer != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(devBroadcastDeviceInterfaceBuffer);
                }
            }
        }

        public static bool UnregisterNotify(IntPtr handle)
        {
            try
            {
                return UnregisterDeviceNotification(handle);
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
        }

        #endregion

        #region Protected Methods

        protected static bool FindDevice(Guid target, ref string path, int instance = 0)
        {
            var detailDataBuffer = IntPtr.Zero;
            var deviceInfoSet = IntPtr.Zero;

            try
            {
                SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(),
                    da = new SP_DEVICE_INTERFACE_DATA();
                int bufferSize = 0, memberIndex = 0;

                deviceInfoSet = SetupDiGetClassDevs(ref target, IntPtr.Zero, IntPtr.Zero,
                    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

                deviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(deviceInterfaceData);

                while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref target, memberIndex,
                    ref deviceInterfaceData))
                {
                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0,
                        ref bufferSize, ref da);
                    {
                        detailDataBuffer = Marshal.AllocHGlobal(bufferSize);

                        Marshal.WriteInt32(detailDataBuffer,
                            (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);

                        if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer,
                            bufferSize, ref bufferSize, ref da))
                        {
                            var pDevicePathName = detailDataBuffer + 4;

                            path = (Marshal.PtrToStringAuto(pDevicePathName) ?? "ERROR").ToUpper();
                            Marshal.FreeHGlobal(detailDataBuffer);

                            if (memberIndex == instance) return true;
                        }
                        else Marshal.FreeHGlobal(detailDataBuffer);
                    }

                    memberIndex++;
                }
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
            finally
            {
                if (deviceInfoSet != IntPtr.Zero)
                {
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
                }
            }

            return false;
        }

        protected virtual bool GetDeviceInstance(ref string instance)
        {
            var detailDataBuffer = IntPtr.Zero;
            var deviceInfoSet = IntPtr.Zero;

            try
            {
                SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(),
                    da = new SP_DEVICE_INTERFACE_DATA();
                int bufferSize = 0, memberIndex = 0;

                deviceInfoSet = SetupDiGetClassDevs(ref _class, IntPtr.Zero, IntPtr.Zero,
                    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

                deviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(deviceInterfaceData);

                while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref _class, memberIndex,
                    ref deviceInterfaceData))
                {
                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0,
                        ref bufferSize, ref da);
                    {
                        detailDataBuffer = Marshal.AllocHGlobal(bufferSize);

                        Marshal.WriteInt32(detailDataBuffer,
                            (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);

                        if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer,
                            bufferSize, ref bufferSize, ref da))
                        {
                            var pDevicePathName = detailDataBuffer + 4;

                            var current = (Marshal.PtrToStringAuto(pDevicePathName) ?? "ERROR").ToUpper();
                            Marshal.FreeHGlobal(detailDataBuffer);

                            if (current == Path)
                            {
                                const int nBytes = 256;
                                var ptrInstanceBuf = Marshal.AllocHGlobal(nBytes);

                                CM_Get_Device_ID(da.Flags, ptrInstanceBuf, nBytes, 0);
                                instance = (Marshal.PtrToStringAuto(ptrInstanceBuf) ?? "ERROR").ToUpper();

                                Marshal.FreeHGlobal(ptrInstanceBuf);
                                return true;
                            }
                        }
                        else Marshal.FreeHGlobal(detailDataBuffer);
                    }

                    memberIndex++;
                }
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
            finally
            {
                if (deviceInfoSet != IntPtr.Zero)
                {
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
                }
            }

            return false;
        }

        protected virtual bool GetDeviceHandle(string path)
        {
            FileHandle = CreateFile(path, (GENERIC_WRITE | GENERIC_READ), FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);

            if (FileHandle == IntPtr.Zero || FileHandle == (IntPtr)INVALID_HANDLE_VALUE)
            {
                FileHandle = IntPtr.Zero;
                var lastError = GetLastError();
                Log.DebugFormat("LastError = {0}", lastError);
            }

            return !(FileHandle == IntPtr.Zero);
        }

        protected virtual bool UsbEndpointDirectionIn(int addr)
        {
            return (addr & 0x80) == 0x80;
        }

        protected virtual bool UsbEndpointDirectionOut(int addr)
        {
            return (addr & 0x80) == 0x00;
        }

        protected virtual bool InitializeDevice()
        {
            try
            {
                var ifaceDescriptor = new USB_INTERFACE_DESCRIPTOR();
                var pipeInfo = new WINUSB_PIPE_INFORMATION();

                if (WinUsbWrapper.QueryInterfaceSettings(_winUsbHandle, 0, ref ifaceDescriptor))
                {
                    for (var i = 0; i < ifaceDescriptor.bNumEndpoints; i++)
                    {
                        WinUsbWrapper.QueryPipe(_winUsbHandle, 0, Convert.ToByte(i), ref pipeInfo);

                        if (((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeBulk) &
                             UsbEndpointDirectionIn(pipeInfo.PipeId)))
                        {
                            BulkIn = pipeInfo.PipeId;
                            WinUsbWrapper.FlushPipe(_winUsbHandle, BulkIn);
                        }
                        else if (((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeBulk) &
                                  UsbEndpointDirectionOut(pipeInfo.PipeId)))
                        {
                            BulkOut = pipeInfo.PipeId;
                            WinUsbWrapper.FlushPipe(_winUsbHandle, BulkOut);
                        }
                        else if ((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeInterrupt) &
                                 UsbEndpointDirectionIn(pipeInfo.PipeId))
                        {
                            IntIn = pipeInfo.PipeId;
                            WinUsbWrapper.FlushPipe(_winUsbHandle, IntIn);
                        }
                        else if ((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeInterrupt) &
                                 UsbEndpointDirectionOut(pipeInfo.PipeId))
                        {
                            IntOut = pipeInfo.PipeId;
                            WinUsbWrapper.FlushPipe(_winUsbHandle, IntOut);
                        }
                    }

                    return true;
                }

                return false;
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
        }

        protected virtual bool RestartDevice(string instanceId)
        {
            var deviceInfoSet = IntPtr.Zero;

            try
            {
                var deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();

                deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
                deviceInfoSet = SetupDiGetClassDevs(ref _class, IntPtr.Zero, IntPtr.Zero,
                    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

                if (SetupDiOpenDeviceInfo(deviceInfoSet, instanceId, IntPtr.Zero, 0, ref deviceInterfaceData))
                {
                    var props = new SP_PROPCHANGE_PARAMS();

                    props.ClassInstallHeader = new SP_CLASSINSTALL_HEADER();
                    props.ClassInstallHeader.cbSize = Marshal.SizeOf(props.ClassInstallHeader);
                    props.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;

                    props.Scope = DICS_FLAG_GLOBAL;
                    props.StateChange = DICS_PROPCHANGE;
                    props.HwProfile = 0x00;

                    if (SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInterfaceData, ref props,
                        Marshal.SizeOf(props)))
                    {
                        return SetupDiChangeState(deviceInfoSet, ref deviceInterfaceData);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message);
                throw;
            }
            finally
            {
                if (deviceInfoSet != IntPtr.Zero)
                {
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
                }
            }

            return false;
        }

        #endregion
    }
}